Singleton.Performance = function() {
	this.tests= {};
	this.groups = [];
	
	this.retry = 5;
	this.no = {group: 0, test: 0, arg: 0};
};

Singleton.Performance.prototype = {
	
	/**
	 * Registers the test group.
	 * @param {name, *before, *after} group
	 */
	registerGroup: function(group) {
		this.tests[group.name] = [];
		this.groups.push(group);
	},
	
	/**
	 * Registers the test for the already registered group
	 * @param {group, name, *args, *retry} test
	 */
	registerTest: function(test) {
		if(!this.tests[test.group]) { throw new Error("Group '" + test.group + "' isn't defined"); }
		
		if(test.off) { return; }
		if(!test.args) { test.args = [1]; }
		this.tests[test.group].push(test);
	},
	
	/**
	 * Sets the default count the single test case will run for calculating the average execution time.
	 * @param Integer retry
	 */
	setRetryCount: function(retry) {
		this.retry = retry;
	},
	
	/**
	 * Starts the console groups for first test from the first group.
	 * @returns
	 */
	setup: function() {
		this._setupGroups();
		
		if(this.getGroup() && this.getTest()) {
			console.group(this.getGroup().name);
			console.group(this.getTest().name);
			return true;
		}
		console.log("No performance tests found.");
		return false;
	},
	
	_setupGroups: function() {
		var arr = [], raw = History.getQueryStringParameter("groups"),
			groups = raw ? raw.replace(/ */g, '').split(',') : [];
		if(!groups.empty()) {
	    	Array.each(this.groups, function(v, k) {
	    		if(groups.contains(v.name)) { arr.push(v); }
	    	});
	    	this.groups = arr;
	    }
	},
	
	/**
	 * Runs the currently selected test 
	 * & schedules next one (if exists) after 30 miliseconds. 
	 */
	run: function() {
		this.profile();
		if(this.nextTest()) { Env.later(100, this, this.run); }
	},
	
	/**
	 * Profiles the selected test case.
	 */
	profile: function() {
		var	group = this.getGroup(),
			test = this.getTest(),
			arg = this.getArg();
				
		var retry = test.retry || this.retry, i = retry,
			run, start = 0, end = 0, arr = [];

		while(i--) {
			if(group.before) { group.before(); }
			if(test.before) { test.before.call(test, arg); }
			
			run = new Date();
			start+= new Date().getTime();
			test.run.call(test, arg);
			
			end+= new Date().getTime();
			arr.push(new Date() - run);
			
			if(test.after) { test.after.call(test, arg); }
			if(group.after) { group.after(); }
		}
		console.log("Arg:", arg, "Average:",  Math.round( (end - start ) / retry ) + " ms", "All:", arr);
	},
	
	/**
	 * Selects next test if exists. Returns true if the selection was successfull, false otherwise.
	 * Handles proper console grouping on group/test start/end.
	 */
	nextTest: function() {
		this.no.arg++;
		
		// Handle test case switching
		if(this.no.arg === this.getTest().args.length) {
			this.no.arg = 0;
			
			do { this.no.test++; }
			while(this.getTest() && this.getTest().off);
			
			if(this.getTest()) {
				console.groupEnd();
				console.group(this.getTest().name);
			}
		}
		
		// Handle group switching
		if(this.no.test === this.tests[this.getGroup().name].length) {
			this.no.test = 0;
			this.no.group++;
			
			if(this.getGroup()) {
				console.groupEnd();
				console.log("");
				console.groupEnd();
				console.group(this.getGroup().name);
				console.group(this.getTest().name);
			}
		}
		
		// Handle no more tests left
		if(this.no.group == this.groups.length) {
			console.groupEnd();
			console.log("");
			console.groupEnd();
			console.log("Performance testing finished.");
			
			this.no.group = 0;
			return false;
		}
		return true;
	},
	
	/**
	 * Returns the selected test case current run argument.
	 */
	getArg: function() {
		return this.getTest().args[this.no.arg]; 
	},
	
	/**
	 * Returns the selected test case.
	 */
	getTest: function() {
		return this.tests[this.getGroup().name][this.no.test];
	},

	/**
	 * Returns the selected group of test cases.
	 */
	getGroup: function() {
		return this.groups[this.no.group];
	}
};

Env.Performance = new Singleton.Performance();